// This file is a part of Funky C++.
// Copyright (C) 2011  Valentin David
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
#ifndef LONG_SAMPLES_HH
# define LONG_SAMPLES_HH

# include "signal.hh"
# include <vector>

namespace funky {

  template <typename Signal, typename WindowSignal,
	    size_t Period, size_t WindowSize>
  struct long_samples {
  private:
    static const size_t WINDOWS =
      (WindowSize + Period - 1)/Period;

    typedef typename signal_traits<Signal>::sample sample;
    typedef typename signal_traits<WindowSignal>::sample window_sample;

    Signal signal;
    std::vector<WindowSignal> windows;

    bus<sample, WindowSize> current[WINDOWS];
    size_t current_window;

  public:
    explicit long_samples(Signal&& signal, const WindowSignal& window):
      signal(std::move(signal)),
      current_window(0) {
      windows.reserve(WINDOWS);
      for (size_t i = 0; i < WINDOWS; ++i) {
	windows.push_back(window);
	windows[i] += (WINDOWS - i) * Period;
      }
      ++*this;
    }

    explicit long_samples(const Signal& signal,
			  const WindowSignal& window):
      signal(signal), current_window(0)
    {
      windows.reserve(WINDOWS);
      for (size_t i = 0; i < WINDOWS; ++i) {
	windows.push_back(window);
	windows[i] += (WINDOWS - i) * Period;
      }
      ++*this;
    }

    long_samples(const long_samples&) = default;

    long_samples(long_samples&& other)
      : signal(std::move(other.signal)),
	windows(std::move(other.windows)),
	current(std::move(other.current)),
	current_window(other.current_window) {
    }

    long_samples& operator=(const long_samples& other) {
      return *this = long_samples(other);
    }

    long_samples& operator=(long_samples&& other) throw() {
      std::swap(signal, other.signal);
      std::swap(windows, other.windows);
      std::swap(current, other.current);
      std::swap(current_window, other.current_window);
      return *this;
    }

    long_samples& operator++() {
      for (size_t i = 0; i < Period; ++i) {
	sample s = *signal;
	for (size_t j = 0; j < WINDOWS; ++j) {
	  size_t window = (current_window + j) % WINDOWS;
	  window_sample f = *windows[window];
	  ++(windows[window]);
	  if ((i+Period*j) < WindowSize)
	    current[window][i+Period*j] = f * s;
	}
	++signal;
      }
      current_window = (current_window + 1) % WINDOWS;
      return *this;
    }

    long_samples operator++(int) {
      long_samples ret{*this};
      ++*this;
      return ret;
    }

    const bus<sample, WindowSize>& operator*() const {
      return current[(current_window+WINDOWS-2)%WINDOWS];
    }
  };

  template <typename Signal, typename sample, size_t Period, size_t WindowSize>
  struct short_samples {
  private:
    static const size_t WINDOWS =
      (WindowSize + Period - 1)/Period;

    Signal signal;
    std::vector<bus<sample, WindowSize> > current;
    size_t current_window;
    size_t position;

  public:
    explicit short_samples(Signal&& signal):
      signal(std::move(signal)),
      current_window(0),
      position(0)
    {
      current.reserve(WINDOWS);
      current.push_back(*(this->signal));
      current.resize(WINDOWS);
    }

    explicit short_samples(const Signal& signal):
      signal(signal),
      current_window(0),
      position(0)
    {
      current.reserve(WINDOWS);
      current.push_back(*(this->signal));
      current.resize(WINDOWS);
    }

    short_samples(const short_samples&) = default;

    short_samples(short_samples&& other)
      : signal(std::move(other.signal)),
	current(std::move(other.current)),
	current_window(other.current_window),
	position(other.position) {}

    short_samples& operator=(const short_samples& other) {
      return *this = short_samples(other);
    }

    short_samples& operator=(short_samples&& other) throw() {
      std::swap(signal, other.signal);
      std::swap(current, other.current);
      std::swap(current_window, other.current_window);
      std::swap(position, other.position);
      return *this;
    }

    short_samples& operator++() {
      ++position;
      if (position >= Period) {
	position -= Period;
	++signal;
	current_window = (current_window + 1)%WINDOWS;
	current[current_window] = *signal;
      }
      return *this;
    }

    short_samples operator++(int) {
      short_samples ret{*this};
      ++*this;
      return ret;
    }

    sample operator*() const {
      sample ret = current[current_window][position];
      for (size_t i = 1; i < WINDOWS; ++i) {
	size_t n = (current_window + i) % WINDOWS;
	ret += current[n][position+i*Period];
      }
      return ret;
    }
  };

  template <size_t Period, size_t WindowSize>
  struct buffer {
    template <typename T, typename RT = typename std::decay<T>::type,
	      typename U,
	      typename = typename signal_traits<RT>::sample,
	      typename = typename signal_traits<U>::sample>
    static long_samples<RT, U, Period, WindowSize>
    to(T&& t, const U& u) {
      return long_samples<RT, U, Period, WindowSize>
	{std::forward<T>(t), u};
    }
  };

  template <size_t Period>
  struct unbuffer {
    template <typename T, typename RT = typename std::decay<T>::type,
	      typename S = typename signal_traits<RT>::sample,
	      typename Sample = typename S::sample,
	      size_t N = S::size>
    static short_samples<RT, Sample, Period, N>
    from(T&& t) {
      return short_samples<RT, Sample, Period, N>{std::forward<T>(t)};
    }
  };

  template <typename T, typename U, size_t N, size_t W>
  struct signal_traits<long_samples<T, U, N, W> > {
    typedef bus<typename signal_traits<T>::sample, W> sample;
    typedef size_t duration;
  };

  template <typename T, typename Sample, size_t N, size_t W>
  struct signal_traits<short_samples<T, Sample, N, W> > {
    typedef Sample sample;
    typedef size_t duration;
  };

}

#endif
